Where in the world is Taylor paddling?

A heatmap based on paddling workouts recorded on my Garmin watch

Taylor true
2025-02-17

Introduction

Over the past two years, I’ve logged dozens of paddling workouts with my Garmin watch. I wanted to visualize where I paddled the most in 2024—so I built this interactive heatmap! Here’s what I learned and how I made it:

Acquiring the data

As any data person will tell you, acquiring the data is usually the most tedious part - and it was. In order to get the data I wanted, I had to log into my Garmin account, filter my activities to water sports, and navigate each individual recorded activity in order to download it as a GPX file. This was in order to preserve the location data associated with each activity, which is not available in the summary .csv available for download.

Processing the data

Once I had an individual file for each activity (87 in total!), I had to aggregate it all into one file. I did this in a separate processing script, so that I could save one small, efficient file to this website and its associated GitHub repo. The code below will not run, it’s just to showcase how I did this processing.

library(sf)
library(xml2)
library(dplyr)
library(purrr)
library(lubridate)
library(jsonlite)

# Define the folder containing GPX files
gpx_folder <- "data/2024 Garmin Data/"

# Get a list of all GPX files in the folder
gpx_files <- list.files(gpx_folder, pattern = "\\.gpx$", full.names = TRUE)
print(gpx_files)  # This should show a list of file paths

# Spot-check 
gpx_sample <- read_xml(gpx_files[1])  # Read first file to make sure it worked
print(gpx_sample)

# Now use a function to parse all of the GPX files:
extract_gpx_distance <- function(file) {
  gpx <- read_xml(file) %>% xml_ns_strip()  # Strip namespace
  
  coords <- gpx %>%
    xml_find_all("//trkpt") %>%
    map_df(~data.frame(
      Latitude = as.numeric(xml_attr(.x, "lat")),
      Longitude = as.numeric(xml_attr(.x, "lon")),
      Timestamp = xml_text(xml_find_first(.x, "time")),  # Extract timestamp
      File = basename(file)  # Keep track of source file
    ))
  
  if (nrow(coords) < 2) {
    return(NULL)  # Skip files with too few points
  }
  
  # Convert Timestamp to proper datetime format
  coords <- coords %>%
    mutate(
      Timestamp = ymd_hms(Timestamp),  # Convert to POSIXct
      Date = as.Date(Timestamp)  # Extract Date separately
    ) %>%
    arrange(Timestamp)  # Ensure chronological order
  
  # Compute distances between consecutive points
  coords <- coords %>%
    mutate(
      Distance_m = c(0, distHaversine(cbind(Longitude[-n()], Latitude[-n()]), 
                                      cbind(Longitude[-1], Latitude[-1]))),  # Compute distances
      Cumulative_Distance_km = cumsum(Distance_m) / 1000  # Convert meters to km
    )
  
  return(coords)
}

# Run the function on all files
all_gpx_data <- map_df(gpx_files, extract_gpx_distance)

# Save data for future use
write.csv(all_gpx_data, "2024_paddling_routes.csv", row.names = FALSE)

# Convert to a spatial object
paddling_sf <- st_as_sf(all_gpx_data, coords = c("Longitude", "Latitude"), crs = 4326)

# Save as GeoJSON
st_write(paddling_sf, "paddling_data.geojson", driver = "GeoJSON", append = FALSE)

And voila! Now I have a GeoJSON file available to make fun maps with!

But first, stats…

General stats

Metric Value
Total Distance Paddled (miles) 603.93
Total Hours Paddled 120.41
Number of Sessions 87
Average Distance per Session (miles) 6.94
Average Duration per Session (mins) 83.04
Earliest Start Time of Day 06:23:11
Latest End Time of Day 11:27:01

On a side note, I don’t believe that last metric - “Latest end time of day” - is correct. If it’s 11pm, that’s wayyy too late to be on the water. If it’s 11am, that’s far too early to be off the water, because most of our practices are in the evenings. I’m sure something just got recorded wrong there.

Trends & Insights

Quite a normal distribution until we get to change season! And you can bet your ass I did absolutely zero paddling in October. A girl’s gotta rest.

Spatial heatmap!

Animation